/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 *
 */

import path from 'path';

import fse from 'fs-extra';
import { glob } from 'glob';

import { BaseCommandBuilder } from '../../builder/base-command-builder.js';
import { NativeConst } from '../../const/common-const.js';
import { DebugSymbol } from '../../options/build/build-opt.js';
import { FileUtil } from '../../utils/file-util.js';
import { LibsFileCacheUtil, NativeLibsCache } from '../../utils/libs-file-cache-util.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ProcessUtils } from '../../utils/process-utils.js';
import { SoStripUtil } from '../../utils/so-strip-util.js';

const logger: OhosLogger = OhosLogger.getLogger('NativeStrip');

export interface StripOptions {
  debugSymbol: DebugSymbol | undefined;
  strip: boolean;
  exclude: string[];
  intermediatesProcessLibs: string;
  sdkLlvmStripPath: string;
  strippedNativeLibs: string;
  moduleName: string;
  taskName: string;
  cacheFilePath: string;
  lastCache: string;
  collectAllLibs: boolean | undefined;
}

export async function strip(stripOptions: StripOptions) {
  if (SoStripUtil.needStrip(stripOptions.debugSymbol)) {
    await stripNativeLibs(stripOptions);
    return;
  }

  await copyFiles(stripOptions);
}

/**
 * 清除掉processStrippedLibsDir中上次编译但本次已经在processLibsDir文件夹下没有了的.so文件以及清除所有软连接
 */
export function clearExpiredSoInStrippedDir(processStrippedLibsDir: string, processLibsDir: string) {
  cleanStrippedSo(processStrippedLibsDir, processLibsDir);
  cleanSymbolicLink(processStrippedLibsDir);
}

async function stripNativeLibs(stripOptions: StripOptions) {
  // 需要strip的文件列表
  let files: string[] = [];

  // 不需要strip的文件列表
  let otherFiles: string[] = [];

  // 如果strip为true 则strip除了符合exclude的 如果strip为false 则只strip符合exclude的
  if (stripOptions.strip) {
    files = SoStripUtil.filesExcluding(stripOptions.collectAllLibs, stripOptions.exclude, stripOptions.intermediatesProcessLibs);
    otherFiles = SoStripUtil.matchedFiles(stripOptions.collectAllLibs, stripOptions.exclude, stripOptions.intermediatesProcessLibs);
  } else if (!stripOptions.strip) {
    files = SoStripUtil.matchedFiles(stripOptions.collectAllLibs, stripOptions.exclude, stripOptions.intermediatesProcessLibs);
    otherFiles = SoStripUtil.filesExcluding(stripOptions.collectAllLibs, stripOptions.exclude, stripOptions.intermediatesProcessLibs);
  }

  // 读取缓存文件 判断每个单独的文件是否需要跳过
  const lastCache: NativeLibsCache = await readCache(stripOptions);

  const stripPromises = files.map(async (filePath) => {
    // 判断源文件是否和上次不同或者目标文件和上次不同 才执行strip 如果源文件是软连接，则不进行strip 只进行复制操作
    const sourceFileAbsolutePath = path.resolve(stripOptions.intermediatesProcessLibs, filePath);
    if (fse.lstatSync(sourceFileAbsolutePath).isDirectory()) {
      return;
    }
    if (fse.existsSync(sourceFileAbsolutePath) && fse.lstatSync(sourceFileAbsolutePath).isSymbolicLink()) {
      return copySoFile(filePath, stripOptions);
    }
    if (await needStripCache(stripOptions, filePath, lastCache)) {
      return executeStrip(filePath, stripOptions);
    }
  });
  const copyPromises = otherFiles.map(async (filePath) => {
    return copySoFile(filePath, stripOptions);
  });
  await Promise.all([...stripPromises, ...copyPromises]);
}

async function copyFiles(stripOptions: StripOptions) {
  // 不需要strip 直接将libs目录下所有文件全部复制到stripped_native_libs目录下
  logger.debug('Do not need strip, copying .so files to stripped_native_libs');

  const otherFiles = glob.sync(stripOptions.collectAllLibs === true ? NativeConst.LIBRARY_ALL_FILE_PATTERN : NativeConst.LIBRARY_FILE_PATTERN, {
    cwd: stripOptions.intermediatesProcessLibs,
  });
  const copyPromises = otherFiles.map(async (filePath) => {
    const stripFileAbsolutePath = path.resolve(stripOptions.strippedNativeLibs, filePath);
    const source = path.resolve(stripOptions.intermediatesProcessLibs, filePath);
    if (!fse.lstatSync(source).isDirectory()) {
      await FileUtil.linkFile(source, stripFileAbsolutePath);
    }
  });
  await Promise.all(copyPromises);
}

async function readCache(stripOptions: StripOptions) {
  const file = fse.existsSync(stripOptions.lastCache) ? stripOptions.lastCache : stripOptions.cacheFilePath;
  return fse.existsSync(file) ? JSON.parse(await fse.readFile(file, { encoding: 'utf-8' })) : { libs: {}, stripped: {} };
}

/**
 * 根据缓存判断是否需要strip
 * @param stripOptions
 * @param filePath
 * @param lastCache
 * @private
 */
function needStripCache(stripOptions: StripOptions, filePath: string, lastCache: NativeLibsCache) {
  return (
    LibsFileCacheUtil.isSourceChanged(
      stripOptions,
      path.resolve(stripOptions.intermediatesProcessLibs, filePath),
      lastCache,
      stripOptions.intermediatesProcessLibs,
      stripOptions.debugSymbol,
    ) || LibsFileCacheUtil.isSinkChanged(path.resolve(stripOptions.strippedNativeLibs, filePath), lastCache)
  );
}

/**
 * 清除掉上次编译但本次已经在libs文件夹下没有了的.so文件
 * @param processStrippedLibsDir
 * @param processLibsDir
 */
function cleanStrippedSo(processStrippedLibsDir: string, processLibsDir: string) {
  if (!fse.existsSync(processStrippedLibsDir)) {
    return;
  }

  // 获取目录中所有文件的绝对路径
  function getFilesInDirectory(dir: string): string[] {
    return fse.readdirSync(dir).reduce((files: string[], file) => {
      const filePath = path.resolve(dir, file);
      if (!fse.existsSync(filePath)) {
        return files;
      }
      const stat = fse.lstatSync(filePath);
      if (stat.isDirectory()) {
        return files.concat(getFilesInDirectory(filePath));
      } else {
        return files.concat(filePath);
      }
    }, []);
  }

  // 获取两个目录中的所有文件路径
  const filesProcessStrippedLibsDir = getFilesInDirectory(processStrippedLibsDir);
  const filesProcessLibsDir = new Set(getFilesInDirectory(processLibsDir).map((file) => path.relative(processLibsDir, file)));

  // 筛选出第一个目录中独有的文件路径
  const uniqueFiles = filesProcessStrippedLibsDir.filter((file) => {
    const relativePath = path.relative(processStrippedLibsDir, file);
    return !filesProcessLibsDir.has(relativePath);
  });

  // 清除掉这部分内容
  uniqueFiles.forEach((filePath) => {
    try {
      fse.unlinkSync(filePath);
    } catch (err) {
      logger.error(`delete filePath: ${filePath} has error: ${err}`);
    }
  });
}

/**
 * 清除dir路径下的所有软连接，如果dir是软连接则删除，如果是文件夹则递归删除里面的所有软连接
 * @param destPath 目标路径
 */
function cleanSymbolicLink(destPath: string): void {
  if (!fse.existsSync(destPath)) {
    return;
  }
  const stats = fse.lstatSync(destPath);
  if (stats.isSymbolicLink()) {
    fse.removeSync(destPath);
  } else if (stats.isDirectory()) {
    fse.readdirSync(destPath).forEach((file) => {
      cleanSymbolicLink(path.resolve(destPath, file));
    });
  }
}

async function copySoFile(sourceFilePath: string, stripOptions: StripOptions) {
  const stripFileAbsolutePath = path.resolve(stripOptions.strippedNativeLibs, sourceFilePath);
  const source = path.resolve(stripOptions.intermediatesProcessLibs, sourceFilePath);
  if (!fse.lstatSync(source).isDirectory()) {
    await FileUtil.linkFile(source, stripFileAbsolutePath);
  }
}

async function executeStrip(sourceFilePath: string, stripOptions: StripOptions) {
  const stripFileAbsolutePath = path.resolve(stripOptions.strippedNativeLibs, sourceFilePath);
  fse.ensureDirSync(path.dirname(stripFileAbsolutePath));
  const llvmCommand = new LlvmCommandBuild(stripOptions.sdkLlvmStripPath)
    .addSourceFilePath(path.resolve(stripOptions.intermediatesProcessLibs, sourceFilePath))
    .addOutputFilePath(stripFileAbsolutePath)
    .build();
  try {
    await new ProcessUtils(stripOptions.moduleName, stripOptions.taskName).execute(llvmCommand);
  } catch (e) {
    logger.warn(e);
    return copySoFile(sourceFilePath, stripOptions);
  }
}

class LlvmCommandBuild extends BaseCommandBuilder {
  constructor(executor: string) {
    super([]);
    this.commandList.push(executor);
  }

  addOutputFilePath(filePath: string) {
    this.commandList.push(`-o${filePath}`);
    return this;
  }

  addSourceFilePath(filePath: string) {
    this.commandList.push(`${filePath}`);
    return this;
  }
}
